/******************************************************************************
 * program:     rasimg library                                                *
 * function:    Executable for image comparison.                              *
 * modul:       gcomp.cc                                                      *
 * licency:     GPL or LGPL                                                   *
 ******************************************************************************/
#include <stddef.h>
#include <stdio.h>

#include <stdlib.h>
#include <string.h>

#include "typedfs.h"
#include "image.h"
#include "imageop.h"

#include <matrix.h>


static char Verbosity = 0;
double Epsilon = 1e-6;

unsigned FuzzyComp = 0;


const char *GetExtension(const char *FullName)
{
const char *NameStep;
int LastPos;

 if(FullName==NULL) return(NULL);

 LastPos=strlen(FullName);
 NameStep=FullName+LastPos+1;
 while(NameStep>FullName)
 {
   NameStep--;

   if(*NameStep=='/' || *NameStep=='\\' || *NameStep==':')
	break;
   if(*NameStep=='.')
	return(NameStep);
 }

return(FullName+LastPos);
}


/////////////////////Main CODE starts here/////////////////////////////////

const char *ExplainType(char IType)
{
  if(IType==ImageNone) return "none";
  if(IType==ImageGray) return "gray";
  if(IType==ImagePalette) return "palette";
  if(IType==ImageTrueColor) return "true color";
  //ImagePaletteF=0x2000,	 //there is used abundant palette
  //ImageReal =   0x4000,  //Real numbers are used
  //ImageSigned = 0x8000	 //image uses signed numbers
  return "?";
}


/** Compare two Images. */
long Compare(const Image *pImg1, const Image *pImg2)
{
uint16_t y,x;
long Differences;
Raster2DAbstract *Ras1;
Raster2DAbstract *Ras2;
Raster1DAbstract *Ras_1;
Raster1DAbstract *Ras_2;

 Differences=0;

 Ras1 = pImg1->Raster;
 Ras2 = pImg2->Raster;

 if(Ras1==NULL || Ras2==NULL) return -1;
 if(Ras1->Data2D==NULL || Ras2->Data2D==NULL) return -2;

 if(Ras1->GetSize1D()!=Ras2->GetSize1D() || Ras1->Size2D!=Ras2->Size2D)
   {
   if(Verbosity>=0)
     {
     printf("  Wrong image size %dx%d compared to %dx%d ",
		Ras1->GetSize1D(),Ras1->Size2D, Ras2->GetSize1D(),Ras2->Size2D);
     }
   return -3;
   }

/* if AlineProc<>nil then
	AlineProc^.InitPassing(Obr1.y,'Comparing 2 images'); */

 if(Verbosity>=0)
   printf("  Image types I1:%X (pl:%d%s)(ch:%d); I2:%X (pl:%d%s)(ch:%d) ", pImg1->ImageType(),Ras1->GetPlanes(), (pImg1->Palette==NULL)?"":"IDX", Ras1->Channels(),
			pImg2->ImageType(),Ras2->GetPlanes(), (pImg2->Palette==NULL)?"":"IDX", Ras2->Channels());

 if(pImg1->ImageType()==ImageTrueColor || pImg2->ImageType()==ImageTrueColor ||
    (pImg1->ImageType()==ImagePalette || pImg2->ImageType()==ImagePalette) )
 {		// True color process is required
   for(y=0; y<Ras1->Size2D; y++)
   {
     for(x=0; x<Ras1->GetSize1D(); x++)
     {
       if(pImg1->GetPixelRGB(x,y) != pImg2->GetPixelRGB(x,y))
       {
          RGBQuad RGB1, RGB2;
          if(pImg1->Raster) pImg1->Raster->Get(x,y, &RGB1);
          if(pImg2->Raster) pImg2->Raster->Get(x,y, &RGB2);
	//uint32_t d = pImg1->GetPixelRGB(x,y);
        //uint32_t e = pImg2->GetPixelRGB(x,y);

	if(Differences<10)
	{
	  printf("\n [%d %d]: [%2.2X %2.2X %2.2X:%2.2X] [%2.2X %2.2X %2.2X:%2.2X]  %lXh %lXh ",x,y,
		RGB1.R, RGB1.G, RGB1.B, RGB1.O, RGB2.R, RGB2.G, RGB2.B, RGB2.O,
		(long)pImg1->GetPixelRGB(x,y), (long)pImg2->GetPixelRGB(x,y));
//	  printf("\n [%d %d]:  %lXh %lXh ",x,y,
//		//RGB1.R, RGB1.G, RGB1.B, RGB1.O, RGB2.R, RGB2.G, RGB2.B, RGB2.O,
//		pImg1->GetPixelRGB(x,y),pImg2->GetPixelRGB(x,y));
	}
	if(Differences==10)
          printf("\nAnd more ...");
        Differences++;
        }
      }
    }
   }
 else if(Ras1->GetPlanes()<=32 && Ras1->GetPlanes()>=-8 && Ras2->GetPlanes()<=32 && Ras2->GetPlanes()>=-8)
   {
   for(y=0; y<Ras1->Size2D; y++)
    {
    Ras_1 = Ras1->GetRowRaster(y);
    Ras_2 = Ras2->GetRowRaster(y);
    if(Ras_1==NULL || Ras_2==NULL) 
      {
      printf("\nEmpty row for line %d",y);
      Differences++;
      continue;
      }

    for(x=0; x<Ras1->GetSize1D(); x++)
      {
      if(Ras_1->GetValue1D(x) != Ras_2->GetValue1D(x))
        {
	if(Differences<10)
          printf("\n [%d %d]: %X %X ",x,y,pImg1->GetPixel(x,y),pImg2->GetPixel(x,y));
	if(Differences==10)
          printf("\nAnd more ...");
	Differences++;
        }
      }
    }
  }
 else
 {
   double MaxDelta = 0;
   for(y=0; y<Ras1->Size2D; y++)
    for(x=0; x<Ras1->GetSize1D(); x++)
    {
      double R1 = Ras1->GetValue2Dd(x,y);
      double R2 = Ras2->GetValue2Dd(x,y);
      double delta = fabs(R1-R2);
      if(delta > MaxDelta) MaxDelta=delta;
      if(delta > Epsilon)
      {
	if(Differences<10)
	  printf("\n [%d %d]: %f %f; eps=%f ", x, y, R1, R2, R1-R2);
	if(Differences==10)
          printf("\nAnd more ...");
	Differences++;
      }
    }
   if(Verbosity>=0) printf("\nMaximal delta = %g", MaxDelta);
 }

return Differences;
}


/** Compare two Images. */
double FuzzyCompare(const Image *pImg1, const Image *pImg2)
{
uint16_t y,x;
double Difference = 0;
Raster2DAbstract *Ras1;
Raster2DAbstract *Ras2;
//Raster1DAbstract *Ras_1;
//Raster1DAbstract *Ras_2;

 Ras1 = pImg1->Raster;
 Ras2 = pImg2->Raster;

 if(Ras1==NULL || Ras2==NULL) return -1;
 if(Ras1->Data2D==NULL || Ras2->Data2D==NULL) return -2;

 if(Ras1->GetSize1D()!=Ras2->GetSize1D() || Ras1->Size2D!=Ras2->Size2D)
   {
   if(Verbosity>=0)
     {
     printf("  Wrong image size %dx%d compared to %dx%d ",
		Ras1->GetSize1D(),Ras1->Size2D, Ras2->GetSize1D(),Ras2->Size2D);
     }
   return -3;
   }

/* if AlineProc<>nil then
	AlineProc^.InitPassing(Obr1.y,'Comparing 2 images'); */

 if(Verbosity>=0)
   printf("  Image types I1:%X (pl:%d%s)(ch:%d); I2:%X (pl:%d%s)(ch:%d) ", pImg1->ImageType(),Ras1->GetPlanes(), (pImg1->Palette==NULL)?"":"IDX", Ras1->Channels(),
			pImg2->ImageType(),Ras2->GetPlanes(), (pImg2->Palette==NULL)?"":"IDX", Ras2->Channels());

 if(pImg1->ImageType()==ImageTrueColor || pImg2->ImageType()==ImageTrueColor ||
    (pImg1->ImageType()==ImagePalette || pImg2->ImageType()==ImagePalette) )
 {		// True color process is required
   RGBQuad RGB1, RGB2;
   for(y=0; y<Ras1->Size2D; y++)
   {
     for(x=0; x<Ras1->GetSize1D(); x++)
     {       
       if(pImg1->Raster) pImg1->Raster->Get(x,y, &RGB1);
       if(pImg2->Raster) pImg2->Raster->Get(x,y, &RGB2);

       Difference += labs((int)RGB1.R-(int)RGB2.R);
       Difference += labs((int)RGB1.G-(int)RGB2.G);
       Difference += labs((int)RGB1.B-(int)RGB2.B);
       Difference += labs((int)RGB1.O-(int)RGB2.O);
     }
   }
   Difference/=Ras1->Size2D;
   Difference/=Ras1->Size1D;
 }
/*
 else if(Ras1->GetPlanes()<=32 && Ras1->GetPlanes()>=-8 && Ras2->GetPlanes()<=32 && Ras2->GetPlanes()>=-8)
   {
   for(y=0; y<Ras1->Size2D; y++)
    {
    Ras_1 = Ras1->GetRowRaster(y);
    Ras_2 = Ras2->GetRowRaster(y);
    if(Ras_1==NULL || Ras_2==NULL) 
      {
      printf("\nEmpty row for line %d",y);
      Differences++;
      continue;
      }

    for(x=0; x<Ras1->GetSize1D(); x++)
      {
      if(Ras_1->GetValue1D(x) != Ras_2->GetValue1D(x))
        {
	if(Differences<10)
          printf("\n [%d %d]: %X %X ",x,y,pImg1->GetPixel(x,y),pImg2->GetPixel(x,y));
	if(Differences==10)
          printf("\nAnd more ...");
	Differences++;
        }
      }
    }
  }
 else
 {
   double MaxDelta = 0;
   for(y=0; y<Ras1->Size2D; y++)
    for(x=0; x<Ras1->GetSize1D(); x++)
    {
      double R1 = Ras1->GetValue2Dd(x,y);
      double R2 = Ras2->GetValue2Dd(x,y);
      double delta = fabs(R1-R2);
      if(delta > MaxDelta) MaxDelta=delta;
      if(delta > Epsilon)
      {
	if(Differences<10)
	  printf("\n [%d %d]: %f %f; eps=%f ", x, y, R1, R2, R1-R2);
	if(Differences==10)
          printf("\nAnd more ...");
	Differences++;
      }
    }
   if(Verbosity>=0) printf("\nMaximal delta = %g", MaxDelta);
 }
*/

  if(Verbosity>=0) printf("\nAverage delta = %f[per px]", Difference);
return Difference;
}


const char* ProfNames[3] = {ExifPreffix, "ICC", "XMP"};

/// Compare property blobs.
void CompareProps(const Image *pImg1, const Image *pImg2)
{
const PropertyItem *Prop1, *Prop2;
unsigned i,j, k;

  for(k=0; k<3; k++)
  {
    const char * const ProfName = ProfNames[k];
    i=0; j=0;
    Prop1 = pImg1->Properties.Find(ProfName,i);
    Prop2 = pImg2->Properties.Find(ProfName,j);
    if(Prop1!=NULL && Prop1->Data!=NULL)
    {
      if(Prop2!=NULL && Prop2->Data!=NULL)
      {
        if(Prop1->DataSize != Prop2->DataSize)
        {
          printf(" Different %s size %u; %u.", ProfName, Prop1->DataSize, Prop2->DataSize);
        }
        else
        {
          if(memcmp(Prop1->Data, Prop2->Data, Prop1->DataSize)==0)
            printf(" %s compare OK", ProfName);
          else
            printf(" Different %ss", ProfName);
        }
      }
      else
      {
        printf(" Image2 has %s, image1 no.", ProfName);
      }
    }
    else
    {
      if(Prop2!=NULL && Prop2->Data!=NULL)
      {
        printf(" Image2 has %s, image1 no.", ProfName);
      }
    }
  }
}


/// Compare multiframe scenes.
long Compare(const Image & Img1, const Image & Img2, double Fuzzy=0)
{
const Image *pI1, *pI2;
long diffs = 0;
int scene=0;

  pI1 = &Img1;
  pI2 = &Img2;
  do
  {
    if(Verbosity>=0)
      if(Img1.Next!=NULL || Img2.Next!=NULL)
        printf("\nScene #%u ", scene++);
    if(Fuzzy==0)
        diffs += Compare(pI1,pI2);
    else
    {
      const double d = FuzzyCompare(pI1,pI2);
      if(d>Fuzzy || d<0) diffs++;
    }
      
    CompareProps(pI1,pI2);
    pI1 = pI1->Next;
    pI2 = pI2->Next;
  } while(pI1!=NULL && pI2!=NULL);

  if((pI1!=NULL || pI2!=NULL) && diffs==0)
  {
    printf("\nUnalligned amount of scenes");
    diffs = -1000;
  }
return diffs;
}


void PrintHeader(void)
{
char HdrPrinted=0;
  if(Verbosity>=0 && HdrPrinted==0)
    {
    printf("\n\r <<< GComp >>> Compare two graphical images (c)1997-2025 F&TSoft, version %u.%u\n", (RAS_IMG_VERSION>>8), (RAS_IMG_VERSION & 0xFF));
    HdrPrinted=1;
    }
}


int main(int argc,char *argv[])
{
Image I1, I2;
const char *Img1=NULL, *Img2=NULL;
const char *s;
TImageFileHandler *LoadPicture_XXX;
int i;
bool Conv2Gray = false;
bool ChannelIDX = false;
int Separate = -1;
int Error = 0;

	/* Parse Arguments */
for(i=1; i<argc; i++)
{
  s=argv[i];
  if(s==NULL) continue;
  if( *s==0 ) continue;

  if(*s=='-' || *s=='/')
  {
    if(strcmp(s+1,"help")==0 || strcmp(s+1,"-help")==0 || strcmp(s+1,"?")==0)
    {
        PrintHeader();
        printf("Supported file formats are:\n");
        for(LoadPicture_XXX=TImageFileHandler::first();LoadPicture_XXX!=NULL;LoadPicture_XXX=LoadPicture_XXX->next())
	{
          if(LoadPicture_XXX->Flags & FILE_FORMAT_DUPLICATE) continue;
	  printf("%s %s%s; ",LoadPicture_XXX->extension(),
			     LoadPicture_XXX->LoadPicture==NULL?"":"R",
			     LoadPicture_XXX->SavePicture==NULL?"":"W");
	}
	printf("\n/s            - silent mode"
	       "\n/v            - verbose"
		//"\n/NoScale      - means truncating image data"
	       "\n/Epsilon  val - Tolerated delta for operations with doubles"
               "\n/Fuzzy [val]  - Nonprecise comparison"
	       "\n/Gray         - discards all color informations"
	       "\n/IDX          - use index channel"
	       "\n/Separate[R|G|B|IDX] - remove one channel from true color images"
	       "\n");
	return(0);
     }

     if(stricmp(s+1,"Epsilon") == 0)
     {
       ++i;
       if(i>=argc) break;
       Epsilon = atof(argv[i]);
       if(Epsilon <= 0)
       {
         printf("\n\rInvalid delta for double values: %f.", Epsilon);
         goto End;
       }
       continue;
     }
    if(strcmp(s+1,"s") == 0)
      {
      Verbosity = -1;
      continue;
      }
    if(strcmp(s+1,"v") == 0)
      {
      Verbosity = +1;
      continue;
      }
    if(stricmp(s+1,"Fuzzy")==0)
      {
      FuzzyComp = 25;
      if(i+1<argc)
      {
        if(isdigit(argv[i+1][0]))
        {
          i++;
          FuzzyComp = atoi(argv[i]);
        }
      }
      continue;
      }
    if(stricmp(s+1,"Gray") == 0)
      {
      Conv2Gray = true;
      continue;
      }
    if(strcmp(s+1,"IDX") == 0)
      {
      ChannelIDX = true;
      continue;
      }
     if(strcmp(s+1,"SeparateR") == 0)
     {
       Separate = 0;
       continue;
     }
     if(strcmp(s+1,"SeparateG") == 0)
     {
       Separate = 1;
       continue;
     }
     if(strcmp(s+1,"SeparateB") == 0)
     {
       Separate = 2;
       continue;
     }
     if(strcmp(s+1,"Separate") == 0)
     {
       ++i;
       if(i>=argc) break;
       if(strcmp(s,"R")==0) Separate=0;
       else if(strcmp(s,"G")==0) Separate=1;
       else if(strcmp(s,"B")==0) Separate=2;
       else if(strcmp(s,"IDX")==0) Separate=10;
       else
       {
         printf("\n\rInvalid channel for separiation: %s.", s);
         goto End;
       }
       continue;
     }

    }

  if(Img1==NULL)
     Img1=s;
  else
     if(Img2==NULL) Img2=s;
  }
/* End of reading arguments */

  PrintHeader();
  if(Verbosity>=0) printf("\rLoading");
  //MarkTime=GetInterval();

  I1 = LoadPicture(Img1);
  if(I1.isEmpty())
	{
	printf("\n\rCannot load 1'st picture:\"%s\" Error %d",Img1,Error);
	Error = -11;
	goto End;
	}

  I2 = LoadPicture(Img2);
  if(I2.isEmpty())
	{
	printf("\n\rCannot load 2'nd picture:\"%s\" Error %d",Img2,Error);
	Error = -12;
	goto End;
	}

  if(Conv2Gray)
  {
    if(Verbosity>=0) printf("\rConverting to gray ");
    Convert2Gray(&I1);
    Convert2Gray(&I2);
  }

  if(ChannelIDX)
  {
    I1.AttachPalette(NULL);
    I2.AttachPalette(NULL);
  }

  if(Separate>=0)
  {
    if(Verbosity>=0) printf("\rSeparating channel %d ",Separate);
    I1 = Peel8bit(I1, Separate);
    I1.AttachPalette(NULL);
    I2 = Peel8bit(I2, Separate);
    I2.AttachPalette(NULL);
  }

  if(Verbosity>=0) printf("\rComparing ");
  i = Compare(I1, I2, FuzzyComp);
  if(i==0)
    {
    if(Verbosity>=0)
      printf("\nBoth images have same contents.\n\r");
    }
  else
    {
    Error = 1;
    if(i<0) printf("\nError %d occured during compare!\n\r",i);
       else printf("\nFound %d mismatches!\n\r",i);
    }
  //MarkTime=GetInterval();

End:
  return(Error);
}

